March 97 - Easy 3D With the QuickDraw 3D Viewer
Easy 3D With the QuickDraw 3D Viewer
Nick Thompson
Ever since QuickDraw 3D shipped in 1995, the QuickDraw 3D
Viewer has made adding 3D support to your application easy. With
QuickDraw 3D version 1.5 we've enhanced the Viewer to make it
even easier to use. We've improved the user interface, added
support for Undo, and rolled in some new API calls. Here you'll
learn how to implement the Viewer to provide simple yet powerful
3D capabilities in your products.
The QuickDraw 3D Viewer provides a way for you to add 3D support to your
application without having to come to grips with the complexity of the whole
QuickDraw 3D programming API. As described in "QuickDraw 3D: A New Dimension
for Macintosh Graphics" in develop Issue 22, full use of QuickDraw 3D requires you to
understand many things before you can get started; for example, you need to be able to
set up data structures to hold not only the geometries being modeled but also the other
elements of a scene, including the lighting, the camera, and the draw context. But
sometimes you just want to be able to display some 3D data in your application without
having to write five pages of setup code.
If this situation sounds familiar to you, the Viewer is tailor-made for your
application. You'll learn all you need to know to be able to use it from reading this
article and examining the accompanying sample applications. Still, you might want to
read the article in Issue 22 as background and to get a sense of how you can use the
Viewer in conjunction with the QuickDraw 3D shared library.
ABOUT THE VIEWER
The QuickDraw 3D Viewer is a high-level shared library, available in both Macintosh
and Windows versions, that's separate from the QuickDraw 3D shared library. With
fewer calls than the full QuickDraw 3D API, the Viewer is a great place to start
exploring QuickDraw 3D. By implementing the Viewer, you can enable users to view
and have a basic level of interaction with 3D data in your application without having to
call any QuickDraw 3D functions. When you need more power, you can always mix
QuickDraw 3D calls with Viewer calls.
The Viewer is ideal for applications that might be described as traditional 2D
applications, such as image database and page layout applications. For example, the
image database Cumulus (from the German developer Canto Software GMbH) is a
traditional 2D application that implements the Viewer to enable users to manipulate
objects in 3D (see Figure 1).
Figure 1. An example of Viewer use in the Cumulus image database
The Viewer gives your application considerable functionality for free. For example,
the Macintosh version of the Viewer supports drag and drop of 3D data. And the Viewer
allows access to the view object (described in detail in the article in develop Issue 22)
so that you can add to your application the capability of changing the lighting, the
camera angles and position, and other things such as the type of renderer being used.
Implementing the Viewer in your application is simple. After going over a few
preliminaries, we'll look in detail at two sample applications -- one just a
bare-bones framework for using the Viewer, and the second a more elaborate
application that implements a fuller set of Viewer features. The source code for both
programs accompanies this article on this issue's CD and develop's Web site.
CHECKING THAT THE VIEWER IS INSTALLED
Before you can use the Viewer, you need to make sure that it's installed. There are two
ways to do this on the Macintosh: you can use Gestalt on System 7 or you can weak-link
against the library and check to see if one of the Viewer routines has been declared
when you launch your application.
You need to call Gestalt with the constant gestaltQD3DViewer, as shown in Listing 1.
The routine IsQD3DViewerInstalled returns a Boolean indicating whether the Viewer
has been installed correctly. The bit selector gestaltQD3DViewerAvailable can be used
to test the appropriate bit of the response from Gestalt.
______________________________
Listing 1. Checking for the Viewer with Gestalt
Boolean IsQD3DViewerInstalled()
{
OSErr theErr;
long gesResponse;

if (Gestalt(gestaltQD3DViewer, &gesResponse) != noErr)
return false;
else
return (gesResponse == gestaltQD3DViewerAvailable);
}
______________________________
The other method is to weak-link against the Viewer library and check the value of one
of the Viewer routines against the constant kUnresolvedCFragSymbolAddress (defined
in CodeFragments.h):
if ((long)Q3ViewerNew != kUnresolvedCFragSymbolAddress) {
... /* Call Viewer routines. */
}
For more information on weak linking (also called soft importing), consult the
documentation that came with your development system. If you use this method, you'll
also need to include the file CodeFragments.h.
DETERMINING THE VIEWER VERSION
Version 1.5 of the Viewer introduces several new API features not found in previous
versions of the Viewer. If you want your application to be compatible with previous
versions of the Viewer, you need to check the version by calling the new routine
Q3ViewerGetVersion. Of course, before you can call this routine, you'll need to test
whether it's been loaded along with the Viewer shared library by checking its address
against the symbol kUnresolvedCFragSymbolAddress. If it hasn't been loaded, you can
safely assume that the Viewer version is 1.0.
Alternatively, you can check the address of each function you need to use against
kUnresolvedCFragSymbolAddress. Listing 2 shows a routine to determine the Viewer
version; this routine works with all versions of the Viewer library.
______________________________
Listing 2. Checking the Viewer version number
OSErr GetViewerVersion(unsigned long *major, unsigned long *minor)
{
/* Version 1.0 of the QuickDraw 3D Viewer had no get version
call, sosee if the symbol for the API routine descriptor
is loaded. */
if ((Boolean)Q3ViewerGetVersion ==
kUnresolvedCFragSymbolAddress) {
*major = 1;
*minor = 0;
return noErr;
}
else
return Q3ViewerGetVersion(major, minor);
}
______________________________
A BARE-BONES FRAMEWORK FOR USING THE VIEWER
Now let's take a look at one of the simplest possible applications we might write to
enable someone to open and view QuickDraw 3D metafiles (files containing 3DMF
data). Of course, this isn't a real Macintosh program -- it opens only one document, it
doesn't respond to Apple events, it doesn't present a menu bar, and the user can't save
changes made in the window. But it does demonstrate that with just five calls to the
Viewer library you can provide good support for 3DMF data in your application. We're
not going to cover anything but the QuickDraw 3D part of this application in any detail,
but the source code is commented well enough so that it should be clear how it works.
THE WINDOW
Figure 2 shows the window from our simple application, called BareBones3DApp. An
instance of the Viewer -- a viewer object -- can occupy an entire window or it can
occupy some smaller portion of a window. In the case of BareBones3DApp, the viewer
object entirely fills the window. The viewer object consists of a controller strip and a
content area outlined with a drag border.
Figure 2. Window from BareBones3DApp
• The controller strip contains a number of buttons for manipulating the
user's point of view (that is, the view's camera). Each of the buttons either
performs a specific function, such as setting a particular camera for the view,
or sets a mode that determines how user interactions are handled. The
controller strip can also be hidden; in this case, a visual element known as
abadge takes its place to indicate to the user that the image in the window
represents a 3D model. The user can click on the badge to make the controller
strip appear.
• The content area (called the picture area in earlier documentation) is
where the 3DMF data is drawn. Users can interact with the object drawn in the
content area in one of several modes, the modes being selected by clicking one
of the buttons in the controller strip. In the default mode that the window
opens up in, users can change the camera angle by dragging across the object.
• The "OpenDoc-style" drag border indicates that the viewer content area
can initiate drags of 3DMF data. By dragging on this border the user can drag
the object displayed in the content area. If dragging into and out of the content
area is enabled (as it is by default), the border will be highlighted when a
drag is initiated to indicate that the content area can receive drops as well.
The part of the window that contains the content area and the controller strip (if
present) is the viewer pane. As an alternative to having the viewer pane entirely fill
the window, you can place the viewer pane in just part of the window, as shown in
Figure 3. This is useful for embedding a 3D picture in a document window.
Figure 3. The viewer pane as part of a window
In the controller strip, the active button is drawn to look as if it's been pressed. The
buttons shown in Figures 2 and 3 are the default ones; you can hide those you don't
want, or make visible the one additional button that's hidden by default, by setting
flags that will be discussed shortly. You can also hide or show the entire controller
strip; you'll see how to do this later.
The full set of available controller buttons is shown in Figure 4. Let's look at each in
turn.
The camera viewpoint button (called the camera angle button in earlier
documentation) enables the user to view the displayed object from a different camera
angle. Holding down the button causes a pop-up menu to appear, listing the predefined
direction cameras as well as any perspective (view angle aspect) cameras stored in
the view hints of the 3DMF data. If any such cameras have name attributes associated
with them in the data, the names are displayed in the pop-up menu; otherwise, the
cameras are listed as "Camera #1," and so on. (The predefined direction cameras are
calculated based on the front and top custom attributes if present in the 3DMF view
hints; otherwise, they're calculated from the displayed object's coordinate space.)
The distance button lets the user move the displayed object closer or farther away.
Clicking the distance button and then dragging downward in the content area moves the
object closer. Dragging upward in the content area moves the object farther away. The
Down Arrow and Up Arrow keys also move the object closer or farther away,
respectively.
The rotate button enables rotating an object. Clicking this button and then dragging in
the content area rotates the displayed object in the direction of the drag. The arrow
keys rotate the object in the direction of the arrow. With version 1.5 of the Viewer
library, you can use the Shift key to constrain the motion of the object as you rotate it.
The zoom button enables the user to alter the field of view of the current camera,
thereby zooming in or out on the displayed object. After the zoom button is clicked,
pressing the Up Arrow and Down Arrow keys zooms the object out and in. By default,
this button isn't displayed.
The move button lets the user move an object. Clicking this button and then dragging in
the content area moves the object to a new location. The arrow keys move the object in
the direction of the arrow.
The reset button resets the camera angle and position to their initial settings.
Figure 4. The full set of available controller buttons
THE BASIC CALLS
As mentioned earlier, you can add support for 3DMF data with calls to just five
routines in the Viewer shared library. These routines, described below, are the ones
we use in BareBones3DApp. For more details on these calls, see the book 3D Graphics
Programming With QuickDraw 3D.
• Q3ViewerNew -- Creates a viewer object and attaches it to a previously
created window, then returns a reference to the viewer object. You need to
pass this reference to other Viewer routines.
• Q3ViewerDispose -- Disposes of the viewer object and associated storage.
You'll probably want to do this just before closing and disposing of the window.
• Q3ViewerSetFile -- Loads a model into the viewer object from a
previously opened 3DMF file. In our program we call StandardGetFile to obtain
the details of the file to open and then open it with the File Manager, passing
Q3ViewerSetFile the file reference the File Manager gave us.
• Q3ViewerEvent -- Gives the viewer object the opportunity to handle
events, then returns a Boolean that indicates whether the event was handled.
• Q3ViewerDraw -- Draws the contents of a viewer object's rectangle in
response to an update event.
THE MAIN ROUTINE
The main routine of BareBones3DApp handles initialization of Macintosh managers,
grows the heap to its maximum size, and checks to see if the QuickDraw 3D Viewer is
installed. There must be at least 24K free in the application heap before a call to
Q3ViewerNew can succeed, so it's important to call the Toolbox routine MaxApplZone to
grow the application heap to its maximum size at the start of the program. Otherwise,
the Viewer may detect (in error) that there's not enough memory to run.
The program then calls the Toolbox routine StandardGetFile to locate a 3DMF file to
open and read. The selected file is opened, and a window is created. The routine to
create a viewer object looks like this:
TQ3ViewerObject Q3ViewerNew(CGrafPtr port, Rect *rect,
unsigned long flags);
Notice that you need to pass in port, rectangle, and flags parameters. It's possible to
create an "empty" viewer object by passing in nil for the port parameter; you can then